package com.compunit.composite.core;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.naming.OperationNotSupportedException;

import org.compunit.Provide;
import org.compunit.Require;
import org.compunit.interfaces.IComponent;

import com.compunit.composite.core.cx.CxExporter;
import com.compunit.composite.core.cx.CxMapping;
import com.compunit.composite.core.cx.CxUnit;
import com.compunit.composite.core.interfaces.IClassExporter;
import com.compunit.composite.core.interfaces.IPackager;
import com.compunit.composite.core.interfaces.IResourceExporter;
import com.compunit.composite.core.interfaces.cx.ICxExporter;
import com.compunit.composite.core.interfaces.cx.ICxMapping;
import com.compunit.composite.core.interfaces.cx.ICxUnit;

/**
 * <code>Packager</code> defines the implementation of
 * the component packaging utility unit.
 *
 * @author Yi Wang (Neakor)
 * @version Creation date: 11-16-2009 22:27 EST
 * @version Modified date: 12-05-2009 14:06 EST
 */
public class Packager implements IPackager {
	/**
	 * The <code>ICxExporter</code> instance.
	 */
	private final ICxExporter cxexporter;
	/**
	 * The <code>IClassExporter</code> instance.
	 */
	private final IClassExporter classexporter;
	/**
	 * The <code>IResourceExporter</code> instance.
	 */
	private final IResourceExporter resourceexporter;
	/**
	 * The <code>List</code> of added <code>Class</code>
	 * due to primary setting.
	 */
	private final List<Class<?>> classesadded;
	/**
	 * The <code>List</code> of added <code>ICxMapping</code>
	 * due to primary setting.
	 */
	private final List<ICxMapping> mappingsadded;
	/**
	 * The <code>Class</code> primary component implementation.
	 */
	private Class<?> primary;
	/**
	 * The <code>ICxUnit</code> for the primary component.
	 */
	private ICxUnit unit;
	
	/**
	 * Constructor of <code>Packager</code>.
	 */
	public Packager() {
		this.cxexporter = new CxExporter();
		this.classexporter = new ClassExporter();
		this.resourceexporter = new ResourceExporter();
		this.classesadded = new ArrayList<Class<?>>();
		this.mappingsadded = new ArrayList<ICxMapping>();
	}

	@Override
	public void seal(final URL directory) throws URISyntaxException, IOException, OperationNotSupportedException {
		final String name = this.cxexporter.getName();
		// Check if there are any resources.
		if(this.resourceexporter.isEmpty()) this.unit.setResourcename(null);
		this.cxexporter.export(directory, name+".cx");
		this.classexporter.export(directory, name+".jar");
		this.resourceexporter.export(directory, name+".resources.jar");
	}

	@Override
	public void export(final URL dir, final String filename) throws URISyntaxException,	IOException, OperationNotSupportedException {
		throw new OperationNotSupportedException("Use seal method to export.");
	}

	@Override
	public void export(final String dir, final String filename) throws URISyntaxException, IOException, OperationNotSupportedException {
		throw new OperationNotSupportedException("Use seal method to export.");
	}

	@Override
	public void addResource(final URL url) throws URISyntaxException {
		this.resourceexporter.addResource(url);
	}

	@Override
	public void addClass(final Class<?> classobject) {
		// Check if already added.
		if(this.classexporter.containsClass(classobject)) return;
		// Add to class exporter.
		this.classexporter.addClass(classobject);
	}

	@Override
	public void removeResource(final String filename) {
		this.resourceexporter.removeResource(filename);
	}

	@Override
	public void removeClass(final Class<?> classobject) {
		this.classexporter.removeClass(classobject);
	}

	@Override
	public boolean containsResource(final String filename) {
		return this.resourceexporter.containsResource(filename);
	}

	@Override
	public boolean containsClass(final Class<?> classobject) {
		return this.classexporter.containsClass(classobject);
	}

	@Override
	public void setPrimary(final Class<?> primary) {
		if(primary == null) throw new IllegalArgumentException("Primary implementation cannot be null");
		try {
			final Object instance = primary.newInstance();
			if(!(instance instanceof IComponent)) {
				throw new IllegalArgumentException("Given class is not a component implementation.");
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		// Remove old cache.
		if(this.primary != null && this.primary != primary) {
			for(final Class<?> c : this.classesadded) {
				this.classexporter.removeClass(c);
			}
			this.classesadded.clear();
			for(final ICxMapping m : this.mappingsadded) {
				this.cxexporter.removeMapping(m);
			}
			this.mappingsadded.clear();
		}
		// Cache provided mappings and interface classes.
		final Provide annop = primary.getAnnotation(Provide.class);
		if(annop != null) {
			final Class<?>[] provided = annop.value();
			for(final Class<?> p : provided) {
				final CxMapping mapping = new CxMapping(true);
				mapping.setInterface(p.getName());
				mapping.addClass(primary.getName());
				this.classesadded.add(p);
				this.classexporter.addClass(p);
				this.mappingsadded.add(mapping);
				this.cxexporter.addMapping(mapping);
			}
		}
		// Cache required mappings and interface classes.
		final Require annor = primary.getAnnotation(Require.class);
		if(annor != null) {
			final Class<?>[] required = annor.value();
			for(final Class<?> r : required) {
				final CxMapping mapping = new CxMapping(false);
				mapping.setInterface(r.getName());
				mapping.addClass(primary.getName());
				this.classesadded.add(r);
				this.classexporter.addClass(r);
				this.mappingsadded.add(mapping);
				this.cxexporter.addMapping(mapping);
			}
		}
		// Set activation.
		this.unit = new CxUnit(primary.getName());
		// Set resources for now, will be removed if no resources
		// during seal operation.
		this.unit.setResourcename(this.getName());
		this.cxexporter.setActivation(new ICxUnit[] {this.unit});
		// Add primary class.
		this.classesadded.add(primary);
		this.classexporter.addClass(primary);
		// Record new primary.
		this.primary = primary;
	}

	@Override
	public void setName(final String name) {
		this.cxexporter.setName(name);
	}

	@Override
	public String getName() {
		return this.cxexporter.getName();
	}

	@Override
	public void cleanup() {
		this.cxexporter.cleanup();
		this.classexporter.cleanup();
		this.resourceexporter.cleanup();
		this.classesadded.clear();
		this.mappingsadded.clear();
		this.primary = null;
	}

	@Override
	public boolean isEmpty() {
		return (this.primary == null);
	}
}
